package com.my.study.nio.channel;

import com.my.study.nio.SteamCloseUtils;
import org.junit.Test;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

/**
 * @author Carlos
 * @version 1.0
 * @Description 通道，用于源节点和目标节点的连接，在java NIO中负责缓冲区中数据的传输。
 *  Channel本身不存储任何数据，需要配合缓存区使用（类似火车轨道和火车）
 *
 * <p>
 *      通过主要实现类
 *      java.nio.channels 接口：
 *          |-- FileChannel
 *          |-- SocketChannel
 *          |-- ServerSocketChannel
 *          |-- DatagramChannel：数据报(UDP)
 * </p>
 *
 * <p>
 *     获取通道
 *    1、本地IO
 *    FileInputStream/FileOutputStream
 *
 *    2、网络IO
 *    Socket
 *    ServerSocket
 *    DatagramSocket
 *
 *    3、JDK1.7中NIO.2 针对各种通道静态的open()
 *    4、JDK1.7中NIO.2 的Files工具类的newByteChannel()
 * </p>
 *
 * <p>
 *     通道之间的数据传输
 *     transferFrom(ReadableByteChannel) -> 从in中获取数据
 *     transferTo(WritableByteChannel)   -> 写入数据
 *
 * </p>
 *
 * <p>
 *     分散(Scatter)和聚集(Gather)
 *     1、分散读取(Scatter)：将通道中的数据分散到多个缓冲区中
 *     2、聚集写入(Gather): 将多个缓冲区的数据聚集到通道中
 *
 * </p>
 *
 * <p>
 *     字符集：Charset
 *     编码：字符串 -> 字节数组
 *     解码：字节数组 -> 字符串
 *
 * </p>
 *
 * @date 2021/7/31 11:36
 **/
public class ChannelTest {


    private final String PREFIX_FILE_PATH = "D:/file";

    /**
     * 非直接缓冲区 - 通道完成文件复制
     */
    @Test
    public void fileCopy() {
        FileInputStream fis = null;
        FileOutputStream fos = null;

        FileChannel inChannel = null;
        FileChannel outChannel = null;


        try {
            fis = new FileInputStream(PREFIX_FILE_PATH + "/ReentrantLock.png");
            fos = new FileOutputStream(PREFIX_FILE_PATH + "/ReentrantLock4.png");

            // 获取通道
            inChannel = fis.getChannel();
            outChannel = fos.getChannel();

            // 分配缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(1024);

            // 将通道的数据写入缓冲区
            while (inChannel.read(buffer) != -1) {
                // 转为读模式
                buffer.flip();
                outChannel.write(buffer);
                // 清空缓冲区，
                buffer.clear();
            }


        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            SteamCloseUtils.close(outChannel, () -> "");
            SteamCloseUtils.close(inChannel, () -> "");
            SteamCloseUtils.close(fos, () -> "");
            SteamCloseUtils.close(fis, () -> "");
        }
    }


    /**
     * 直接缓冲区 - 通道完成文件复制
     */
    @Test
    public void directFileCopy() {
        FileChannel inChannel = null;
        FileChannel outChannel = null;

        try {
            inChannel = FileChannel.open(Paths.get(PREFIX_FILE_PATH + "/ReentrantLock.png"), StandardOpenOption.READ);
            // 这也要设置读写模式，与下面的outMappedBuf对应
            outChannel = FileChannel.open(Paths.get(PREFIX_FILE_PATH + "/ReentrantLock5.png"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE_NEW);

            // 内存文件映射
            MappedByteBuffer inMapperBuf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
            MappedByteBuffer outMappedBuf = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());


            // 直接对缓冲区进行数据读写
            byte[] dst = new byte[inMapperBuf.limit()];
            inMapperBuf.get(dst);
            outMappedBuf.put(dst);

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            SteamCloseUtils.close(outChannel, () -> "");
            SteamCloseUtils.close(inChannel, () -> "");
        }
    }

    /**
     * 直接缓冲区 - 通道之间的数据传输
     */
    @Test
    public void channelTransfer() {
        FileChannel inChannel = null;
        FileChannel outChannel = null;

        try {
            inChannel = FileChannel.open(Paths.get(PREFIX_FILE_PATH + "/ReentrantLock.png"), StandardOpenOption.READ);
            // 这也要设置读写模式，与下面的outMappedBuf对应
            outChannel = FileChannel.open(Paths.get(PREFIX_FILE_PATH + "/ReentrantLock6.png"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE_NEW);

            // 将in通道的数据传输到out通道中
            // outChannel.transferFrom(inChannel, 0, inChannel.size());
            inChannel.transferTo(0, inChannel.size(), outChannel);

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            SteamCloseUtils.close(outChannel, () -> "");
            SteamCloseUtils.close(inChannel, () -> "");
        }


    }

    /**
     * 分散与聚集
     */
    @Test
    public void scatterAndGather() {
        RandomAccessFile inFile = null;
        RandomAccessFile outFile = null;

        try {
            inFile = new RandomAccessFile(PREFIX_FILE_PATH + "/ReentrantLock.png", "rw");
            outFile = new RandomAccessFile(PREFIX_FILE_PATH + "/ReentrantLock7.png", "rw");
            // 获取通道
            FileChannel inChannel = inFile.getChannel();
            FileChannel outChannel = outFile.getChannel();


            // 分配多个缓冲区
            ByteBuffer buf1 = ByteBuffer.allocate(24);
            ByteBuffer buf2 = ByteBuffer.allocate(1000);

            // 分散读取
            ByteBuffer[] buffers = {buf1, buf2};
            while (inChannel.read(buffers) != -1) {
                for (ByteBuffer buffer : buffers) {
                    buffer.flip();
                }

                // 聚集写入
                outChannel.write(buffers);

                for (ByteBuffer buffer : buffers) {
                    buffer.clear();
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            SteamCloseUtils.close(inFile, () -> "");
            SteamCloseUtils.close(outFile, () -> "");
        }
    }

}
